package io.legado.app.ui.association

import android.app.Application
import androidx.lifecycle.MutableLiveData
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.ReplaceAnalyzer
import io.legado.app.help.http.newCallResponseBody
import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isJsonArray
import io.legado.app.utils.isJsonObject
import io.legado.app.utils.splitNotBlank

class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
    var isAddGroup = false
    var groupName: String? = null
    val errorLiveData = MutableLiveData<String>()
    val successLiveData = MutableLiveData<Int>()

    val allRules = arrayListOf<ReplaceRule>()
    val checkRules = arrayListOf<ReplaceRule?>()
    val selectStatus = arrayListOf<Boolean>()

    val isSelectAll: Boolean
        get() {
            selectStatus.forEach {
                if (!it) {
                    return false
                }
            }
            return true
        }

    val selectCount: Int
        get() {
            var count = 0
            selectStatus.forEach {
                if (it) {
                    count++
                }
            }
            return count
        }

    /**
     * 执行导入选择操作
     *
     * 此函数负责根据当前的选择状态和规则，将选定的规则导入数据库它首先会根据用户的选择构建一个规则列表，
     * 然后将这些规则插入到数据库中在插入规则之前，如果用户指定了组名，则会根据[isAddGroup]标志决定是否将组名
     * 添加到规则的组列表中，或者直接替换规则的组名为新指定的组名
     *
     * @param finally 一个lambda表达式，表示导入选择操作完成后需要执行的操作
     */
    fun importSelect(finally: () -> Unit) {
        execute {
            // 获取并修剪组名
            val group = groupName?.trim()
            // 初始化一个数组列表来存储选定的规则
            val selectRules = arrayListOf<ReplaceRule>()
            // 遍历选择状态数组，确定哪些规则被选中
            selectStatus.forEachIndexed { index, b ->
                if (b) {
                    // 获取当前选中的规则
                    val rule = allRules[index]
                    // 如果组名非空且用户选择添加组名，则将组名添加到规则的组列表中，否则直接替换规则的组名
                    if (!group.isNullOrEmpty()) {
                        if (isAddGroup) {
                            // 创建一个集合来存储规则的组名列表
                            val groups = linkedSetOf<String>()
                            // 将规则的组名按正则表达式分割并添加到集合中
                            rule.group?.splitNotBlank(AppPattern.splitGroupRegex)?.let {
                                groups.addAll(it)
                            }
                            // 将新指定的组名添加到集合中
                            groups.add(group)
                            // 将集合中的组名用逗号连接成字符串并赋值给规则的组名
                            rule.group = groups.joinToString(",")
                        } else {
                            // 直接替换规则的组名为新指定的组名
                            rule.group = group
                        }
                    }
                    // 将处理后的规则添加到选定规则列表中
                    selectRules.add(rule)
                }
            }
            // 将选定的规则插入数据库
            appDb.replaceRuleDao.insert(*selectRules.toTypedArray())
        }.onFinally {
            // 导入选择操作完成后执行传入的lambda表达式
            finally.invoke()
        }
    }

    /**
     * 导入文本数据
     *
     * 该函数负责将提供的文本数据进行导入处理如果导入过程中遇到错误，
     * 会将错误信息发送到UI线程并记录日志成功导入后，会调用comparisonSource函数
     * 进行后续处理
     *
     * @param text 需要导入的文本数据
     */
    fun import(text: String) {
        // 执行导入操作
        execute {
            // 对输入的文本进行空格修剪后调用导入方法
            importAwait(text.trim())
        }.onError {
            // 导入失败时，发送错误信息到UI线程并记录日志
            errorLiveData.postValue("ImportError:${it.localizedMessage}")
            AppLog.put("ImportError:${it.localizedMessage}", it)
        }.onSuccess {
            // 导入成功时，调用比较源数据的方法
            comparisonSource()
        }
    }

    /**
     * 根据不同的文本格式异步导入数据
     *
     * 此函数旨在根据输入文本的格式（绝对URL、JSON数组或JSON对象）来导入相应的数据
     * 它使用when表达式来判断文本格式，并根据格式执行不同的导入操作
     *
     * @param text 待导入的文本，可以是绝对URL、JSON数组或JSON对象格式
     * @throws NoStackTraceException 当文本格式不符合预期时抛出此异常
     */
    private suspend fun importAwait(text: String) {
        when {
            // 当文本是绝对URL格式时，调用importUrl函数进行导入
            text.isAbsUrl() -> importUrl(text)
            // 当文本是JSON数组格式时，将其转换为替换规则并添加到allRules中
            text.isJsonArray() -> {
                val rules = ReplaceAnalyzer.jsonToReplaceRules(text).getOrThrow()
                allRules.addAll(rules)
            }
            // 当文本是JSON对象格式时，将其转换为单个替换规则并添加到allRules中
            text.isJsonObject() -> {
                val rule = ReplaceAnalyzer.jsonToReplaceRule(text).getOrThrow()
                allRules.add(rule)
            }
            // 当文本格式不符合上述任何一种情况时，抛出异常
            else -> throw NoStackTraceException("格式不对")
        }
    }

    /**
     * 暂挂函数，用于导入给定URL的内容
     * 此函数使用OkHttpClient发起网络请求，并根据URL的特定后缀决定是否发送User-Agent头部
     * 如果URL以"#requestWithoutUA"结尾，则不会发送User-Agent头部，否则将正常发送
     * 请求成功后，将返回的内容传递给importAwait函数进行后续处理
     *
     * @param url 要导入内容的URL，可能包含特殊的后缀以指示请求行为
     */
    private suspend fun importUrl(url: String) {
        okHttpClient.newCallResponseBody {
            // 根据URL是否以"#requestWithoutUA"结尾来决定是否发送User-Agent头部
            if (url.endsWith("#requestWithoutUA")) {
                url(url.substringBeforeLast("#requestWithoutUA"))
                header(AppConst.UA_NAME, "null")
            } else {
                url(url)
            }
        }.text("utf-8").let {
            importAwait(it)
        }
    }

    /**
     * 从数据库中比较规则源
     * 此函数遍历所有规则，检查它们是否存在于数据库中，并根据检查结果更新状态
     */
    private fun comparisonSource() {
        // 执行异步操作
        execute {
            // 遍历所有规则
            allRules.forEach {
                // 通过ID从数据库中查找规则
                val rule = appDb.replaceRuleDao.findById(it.id)
                // 将找到的规则添加到检查规则列表中
                checkRules.add(rule)
                // 如果规则不存在于数据库中，则选择状态为true，否则为false
                selectStatus.add(rule == null)
            }
        // 当异步操作成功完成时
        }.onSuccess {
            // 发送成功信号，携带所有规则的数量
            successLiveData.postValue(allRules.size)
        }
    }
}